package main;
import java.util.ArrayList;
import org.xml.sax.*;
import org.xml.sax.helpers.*;

/**
 * The Class SaxHandler.
 */
public class SaxHandler extends DefaultHandler {

	/** The tag found boolean variable. */
	private boolean tagFound = false;

	/** The recursive boolean variable. */
	private boolean recursive = false;

	/** The all tags boolean variable. */
	private boolean allTags = false;

	/** The output. */
	private StringBuilder output;

	/** The path. */
	private ArrayList<String> path;

	/** The current path. */
	private ArrayList<String> currentPath;

	/** The recursion path. */
	private ArrayList<String> recursionPath;
	/** The element. */
	private String element = "";

	/** The file. */
	private String file;

	/**
	 * The writer.
	 * @uml.property  name="writer"
	 * @uml.associationEnd  
	 */
	private ReaderWriter writer;

	/** The end time. */
	private final long endTime;

	/** The start time. */
	private final long startTime;

	/**
	 * Instantiates a new sax handler.
	 * 
	 * @param expression
	 *            the expression
	 * @param file
	 *            the file
	 */
	public SaxHandler(String expression, String file) {
		writer = new ReaderWriter();
		this.output = new StringBuilder(1000);
		this.path = new ArrayList<String>();
		this.currentPath = new ArrayList<String>();
		this.recursionPath = new ArrayList<String>();
		this.output.append("xPath expression: ");
		this.output.append(expression);
		this.output.append("\n");
		this.file = file;
		startTime = System.nanoTime();
		parseExpression(expression);
		endTime = System.nanoTime();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#startDocument()
	 */
	public void startDocument() throws SAXException {

		final long duration = endTime - startTime;
		double durationInSec = (double) duration / 1000000000.0;
		this.output.append("Expression parsed in " + duration + " ns ("
				+ durationInSec + " s)\n");
		this.output.append("Path: ");
		this.output.append(path);
		this.output.append(" Recursion Path: ");
		this.output.append(recursionPath);
		this.output.append(" Element: ");
		this.output.append(element);
		this.output.append("\n\n");
		System.out.println(output);
		output = new StringBuilder(1000);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String,
	 * java.lang.String, java.lang.String, org.xml.sax.Attributes)
	 */
	public void startElement(String namespaceUri, String localName,
			String qualifiedName, Attributes attributes) throws SAXException {
		// add the element to the path since we are there
		this.currentPath.add(qualifiedName);
		if (matchPath()) {
			this.tagFound = true;
			// output.append("\n");
		} else
			this.tagFound = false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String,
	 * java.lang.String, java.lang.String)
	 */
	public void endElement(String namespaceUri, String localName,
			String qualifiedName) throws SAXException {
		// remove the last element from the path since we are leaving from there
		if (this.currentPath.size() > 1) {
			this.currentPath.remove(currentPath.size() - 1);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
	 */
	public void characters(char[] chars, int startIndex, int endIndex) {
		if (this.tagFound) {
			if (output.length() > 900) {
				if (file.isEmpty()) {
					System.out.println(output);
				} else {
					this.writer.WriteFile(file, output);
				}
				output = new StringBuilder(1000);
			}
			String dataString = new String(chars, startIndex, endIndex).trim();
			if (!dataString.isEmpty()) {
				this.output.append("\t");
				this.output.append(dataString);
				output.append("\n");
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#endDocument()
	 */
	public void endDocument() throws SAXException {
		System.out.println(output);
		if (!file.isEmpty())
			this.writer.WriteFile(file, output);
	}

	/**
	 * Match path.
	 * 
	 * @return true, if successful
	 */
	private boolean matchPath() {
		// if expression is *
		if (path == null & element == null & allTags)
			return true;
		int currentPathSize = currentPath.size();
		int pathSize = path.size();
		int recursionPathSize = recursionPath.size();
		if (path.isEmpty() & !recursive) {
			// we only have an element
			// if the element is the root element show it
			// else return false
			if (element.equals(currentPath.get(0)))
				return true;
			else
				return false;
		}
		// check if we are at the correct element
		if (this.element.equals("*")
				| this.currentPath.get(currentPathSize - 1).equals(this.element)
				| this.currentPath.contains(this.element)) {
			// we have an element we care. let's check if we have a recursion or
			// a path location
			if (pathSize + 1 <= currentPathSize & !recursive) {
				if (!this.element.equals("*")) {
					if (!currentPath.get(pathSize).equals(this.element))
						return false;
				}
				// if (absolutePath&currentPathSize>pathSize+1) return false;
				// if we have no recursion but a path location
				// by arriving here we know that the element is the current
				// qualified name
				// let's check the whole path if it is correct
				for (int i = 0; i < pathSize; i++) {
					String test = path.get(i);
					// exclude asterisks and match the elements names
					if (!test.contains("*")) {
						if (!currentPath.get(i).equals(test))
							return false;
					}
				}
				// by arriving here we have a match
				return true;
			}
			if (recursive) {
				// if we have a recursion with or wthout recursion path
				// first check the depth
				// if the currentPath is less in size than the path size + the
				// recursion size
				// then obviously we do not care about that element
				if (currentPathSize < pathSize + recursionPathSize + 1)
					return false;

				// then check first the path by one to one matching
				// because a matching element must have the path defined in the
				// expression
				for (int i = 0; i < pathSize; i++) {
					// exclude asterisks and match the elements names
					if (!path.get(i).equals("*")& !currentPath.get(i).equals(path.get(i)))
						return false;
				}
				// last lets check the recursive path by one to one matching
				// because a matching element must also have the recursion path
				// defined in the expression
				if (recursionPathSize > 0) {
					int fIndex = currentPath.indexOf(recursionPath.get(0));
					if (fIndex == -1)
						return false;
					for (int i = 0; i < recursionPathSize; i++) {
						// exclude asterisks and match the elements names
						if (!recursionPath.get(i).equals("*")) {
							if (!currentPath.get(fIndex + i).equals(recursionPath.get(i))) return false;
						}
					}
					// now check if the element follows the recursion path
					if (!this.element.equals("*"))
						if (!this.element.equals(currentPath.get(fIndex+recursionPathSize)))
							return false;
				}
				// if all checks have passed return true
				return true;
			}
		} else
			return false;
		return false;
	}

	/**
	 * Parses the expression.
	 * 
	 * @param expression
	 *            the expression
	 * @return true, if successful
	 */
	private boolean parseExpression(String expression) {
		// figure out which is the path , which is the recursion path and which
		// is the element
		int expressionLastIndexOfSlash = expression.lastIndexOf("/");
		if (!expression.equals("*")) {
			if (expression.charAt(expression.length() - 1) != '/') {
				// now split the path with the element
				if (expression.contains("//")) {
					recursive = true;
					if (expression.lastIndexOf("//") != 0) {
						String temp[] = expression.substring(0,
								expression.lastIndexOf("//")).split("/");
						for (String s : temp) {
							if (!s.isEmpty())
								this.path.add(s);
						}
					}
					this.element = expression.substring(expression
							.indexOf("//") + 2);
					if (this.element.contains("/")) {
						String temp[] = element.split("/");
						for (int i = 0; i < temp.length - 1; i++) {
							if (!temp[i].isEmpty())
								this.recursionPath.add(temp[i]);
						}
						element = element
						.substring(element.lastIndexOf("/") + 1);
					}
					// this.allTags = true;
				} else if (expression.contains("/")) {
					String temp[] = expression.substring(0,
							expressionLastIndexOfSlash).split("/");
					for (int i = 0; i < temp.length; i++) {
						if (!temp[i].isEmpty())
							this.path.add(temp[i]);
					}
					this.element = expression
					.substring(expressionLastIndexOfSlash + 1);
				} else {
					this.element = expression;
				}
			} else {
				System.out
				.println("A location step was expected following the '/' or '//' token.");
				return false;
			}
		} else {
			allTags = true;
			path = null;
			element = null;
		}
		return true;
	}
}